/*
	jGrouse framework
	Copyright 2007 by Denis Riabtchik 
	All rights reserved.
	
	Licensed under modified BSD license. See license.txt for details
*/


/**
	Different implementations of Map functionality
	@module jgrouse.util.Maps
	@requires module-jgrouse.base
	@requires module-jgrouse.coreutil
*/

jgrouse.module(
{
	name : 'jgrouse.util.Maps',
	requires : ['jgrouse.base', 'jgrouse.coreutil'], 
	body : function(embed)
{
	eval(embed);
	
	/**
		Entry in a map
		@interface jgrouse.util.Map.Entry
	*/
	/**
		@variable {Object} key - key corresponding to the entry
	*/
	/**
		@variable {Object} value - value corresponding to the entry
	*/

	/**
		Abstract Map - an object that maps key to values. 
		@class jgrouse.util.Map
	*/
	jgrouse.define('jgrouse.util.Map', 'jgrouse.BaseObject', function(salt)
	{
		eval(salt);
		
		return {
			/**
				@constructor initialize
			*/
			initialize : function()
			{
				_super(this, 'initialize');
			},
			
			/**
				Check if given key exists in map
				@function {boolean} exists
				@param {Object} key - key to search for
				@return true if the key exists in map
			*/
			exists : function(key)
			{
				jgrouse.notImplemented(this.RTTI + '.exists');
			},
			
			/**
				Asssociates given element with specified value in the map.
				If an element with such key existed in map before, it would be returned.
				@function {Object} put
				@param {Object} key - key with which the value should be associated
				@param {Object} value - value to be added
				@return previous value associated with the key, if applicable
			*/
			put : function(key, value)
			{
				jgrouse.notImplemented(this.RTTI + '.put');
			},
			
			/**
				Returns value previously associated with the key or undefined if the map does not contain the key.
				@function {Object} get
				@param {Object} key - key whose associated value should be returned
				@return either mapped object or undefined if key does not exist
			*/
			get : function(key)
			{
				jgrouse.notImplemented(this.RTTI + '.get');
			},
			
			/**
				Removes key and associated value from the map
				@function {Object} remove
				@param {Object} key - key whose associated value should be removed
				@return previously associated value or undefined if the key was not found in the map
			*/
			remove : function(key)
			{
				jgrouse.notImplemented(this.RTTI + '.remove');
			},
			
			/**
				Returns an array of {@link Entry} objects that exist in the map
				@function {Entry[]} items
				@return array of {@link Entry} objects
			*/
			items : function()
			{
				jgrouse.notImplemented(this.RTTI + '.items');
			},
			
			/**
				Returns an array containing all keys from the map
				@function {Object[]} keys
			*/
			keys : function()
			{
				jgrouse.notImplemented(this.RTTI + '.keys');
			},
			
			/**
				Returns an array containing all values from the map
				@function {Object[]} values
			*/
			values : function()
			{
				jgrouse.notImplemented(this.RTTI + '.values');
			},
			
			/**
				Returns an array of {@link Entry} objects containing all items in the map. 
				@function {Entry[]} toArray
				@return array of {@Entry} objects
				
			*/
			toArray : function()
			{
				return this.items();
			},
			
			/**
				Call a callback function for all entries in the map
				@function forEach
				@param {objectIteratorCallback} iterator - callback to be called
				@param {optional Boolean} backwards - if iteration should start from end (optional, if applicable}
			*/
			forEach : function(iterator, backwards)
			{
			},
			
			/**
				Clear contents of the map
				@function clear
			*/
			clear : function()
			{
			}
		}
	});
	
	
	
	/**
		Implementation of map where the key could be any object. 
		@class jgrouse.util.ObjectMap
		@extends jgrouse.util.Map
	*/
	jgrouse.define('jgrouse.util.ObjectMap', 'jgrouse.util.Map', function(salt)
	{
		eval(salt);
		
		return {
			/**
				@constructor initialize
				@param {function(key, item} comparator - function that returns true if passed key equals to item, optional
			*/
			initialize : function(comparator)
			{
				_super(this, 'initialize');
				this._data = [];
				this._comparator = !!comparator? comparator : this._findKey;
			},
			
			_findKey : function(key, item)
			{
				return (key === item.key);			
			},
			
			_search : function(key)
			{
				return jgrouse.search(this._data, key, this._comparator);
			},
			
			/**
				@function exists
				@inheritdesc
			*/
			exists : function(key)
			{
				return this._search(key) === 0;
			},
			
			/**
				@function put
				@inheritdesc
			*/
			put : function(key, value)
			{
				var index = this._search(key);
				if (index >= 0)
				{
					var old = this._data[index].value;
					this._data[index].value = value;
					return old;
				}
				else
				{
					this._data.push({key: key, value: value});
					return undefined;
				}
			},
			
			/**
				@function get
				@inheritdesc
			*/
			get : function(key)
			{
				var index = this._search(key);
				return (index >= 0)? this._data[index].value : undefined;
			},
			
			/**
				@function remove
				@inheritdesc
			*/
			remove : function(key)
			{
				var index = this._search(key);
				if (index >= 0)
				{
					var item = this._data[index].value;
					this._data.splice(index, 1);
					return item;
				}
				return undefined;
			},
			
			/**
				@function items
				@inheritdesc
			*/
			items : function()
			{
				return [].concat(this._data);
			},
			
			/**
				@function keys
				@inheritdesc
			*/
			keys : function()
			{
				var res = [];
				jgrouse.forEach(this._data, function(item)
				{
					res.push(item.key);
				});
				return res;
			},
			
			/**
				@function values
				@inheritdesc
			*/
			values : function()
			{
				var res = [];
				jgrouse.forEach(this._data, function(item)
				{
					res.push(item.value);
				})
				return res;
			},
			
			/**
				@function forEach
				@inheritdesc
			*/
			forEach : function(iterator, backwards)
			{
				jgrouse.forEach(this._data, function(entry, index)
				{
					iterator(entry.value, entry.key);
				}, backwards);
			},
			
			/**
				@function clear
				@inheritdesc
			*/
			clear : function()
			{
				this._data = [];
			}
			
		}
	});
	
	/**
		Implementation of a simple map where keys are strings or objects that could be converted to string.
		@class jgrouse.util.SimpleMap
		@extends jgrouse.util.Map
	*/
	jgrouse.define('jgrouse.util.SimpleMap', 'jgrouse.util.Map', function(salt)
	{
		eval(salt)
		
		return {
			/**
				@constructor initialize
				@param {Object} data - initial values for map
			*/
			initialize : function(data)
			{
				_super(this, 'initialize');
				this._data = data? data : {};
			},
			
			/**
				@function exists
				@inheritdesc
			*/
			exists : function(key)
			{
				return this._data[key] != undefined;
			},
			
			/**
				@function put
				@inheritdesc
			*/
			put : function(key, value)
			{
				var old = this._data[key];
				this._data[key] = value;
				return old;
			},
			
			/**
				@function get
				@inheritdesc
			*/
			get : function(key)
			{
				return this._data[key];
			},
			
			/**
				@function remove
				@inheritdesc
			*/
			remove : function(key)
			{
				var item = this._data[key];
				delete this._data[key];
				return item;
			},
			
			/**
				@function forEach
				@inheritdesc
			*/
			forEach : function(iterator)
			{
				var d = {};
				try
				{
					for (var i in this._data)
					{
						var v = this._data[i];
						if (v != d[i])
						{
							iterator(v, i);
						}
					}
				}
				catch (ex)
				{
					if (ex != jgrouse.$break)
					{
						throw ex;
					}
				}
			},
			
			/**
				@function items
				@inheritdesc
			*/
			items : function()
			{
				var result = [];
				this.forEach(function(value, key)
				{
					result.push({key: key, value: value});
				})
				return result;
			},
			
			/**
				@function keys
				@inheritdesc
			*/
			keys : function()
			{
				var res = [];
				this.forEach(function(value, key)
				{
					res.push(key);
				})
				return res;
			},
			
			/**
				@function values
				@inheritdesc
			*/
			values : function()
			{
				var res = [];
				this.forEach(function(value, key)
				{
					res.push(value);
				})
				return res;
			},
			
			/**
				@function toArray
				@inheritdesc
			*/
			toArray : function()
			{
				return this.items();
			},
			
			/**
				@function clear
				@inheritdesc
			*/
			clear : function()
			{
				this._data = {};
			}
		}
	})
	
	
}});